﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ADDDF.Infrastructure.Collections
{
    public class EntitySet<T> : IEntitySet<T>, IEntitySet
        where T: IEntity
    {
        private List<T> _entities;
        
        #region Constructors

        public EntitySet()
            : this(null)
        {            
        }

        public EntitySet(IEnumerable<T> entities)            
        {
            if (entities == null)
                _entities = new List<T>();
            else
                _entities = new List<T>(entities);
        }

        #endregion
        
        #region IEntitySet<T> implementation

        public T FindByKey(object key)
        {
            return FindReferenceByKey(key);
        }

        public IEnumerable<T> GetAll()
        {
            return _entities.AsEnumerable();
        }

        public bool AddAndReplace(T entity)
        {
            bool removed = Remove(entity);

            _entities.Add(entity);

            return removed;
        }

        public bool Remove(T entity)
        {
            T found = FindReferenceByKey(entity.Key);

            if (found == null)
                return false;

            _entities.Remove(found);

            return true;
        }

        public void RemoveAll()
        {
            _entities.Clear();
        }

        public int Count()
        {
            return _entities.Count();
        }

        public IEnumerable<T> Subtract(IEntitySet<T> another)
        {
            List<T> result = new List<T>();

            foreach (T entity in _entities)
                if (another.FindByKey(entity) == null)
                    result.Add(entity);

            return result;
        }

        public IEnumerable<T> Intersect(IEntitySet<T> another)
        {
            List<T> result = new List<T>();

            foreach (T entity in _entities)
                if (another.FindByKey(entity) != null)
                    result.Add(entity);

            return result;
        }

        #endregion
        
        #region Private Members

        private T FindReferenceByKey(object key)
        {
            foreach (T entity in _entities)
            {
                if (KeyEquals(entity.Key, key))
                    return entity;
            }

            return default(T);
        }

        private bool KeyEquals(object key1, object key2)
        {
            if (key1 == null && key2 == null)
                throw new InvalidOperationException("At least one of the keys in comparison must not be null");

            if (object.ReferenceEquals(key1, key2))
                return true;

            if (key1 == null)
                return key2.Equals(key1);
            else
                return key1.Equals(key2);
        }

        #endregion
                
        #region IEntitySet implementation

        public void FindByKey(object key, out IEntity result)
        {
            result = this.FindByKey(key);
        }

        public void GetAll(out IEnumerable<IEntity> result)
        {
            result = this.GetAll().Select(x => (IEntity)x);
        }

        public bool AddAndReplace(IEntity item)
        {
            return this.AddAndReplace((T) item);
        }

        public bool Remove(IEntity item)
        {
            return this.Remove((T)item);
        }

        public void Subtract(IEntitySet another, out IEnumerable<IEntity> result)
        {
            IEnumerable<IEntity> itemsFromAnother;

            another.GetAll(out itemsFromAnother);

            EntitySet<T> typedAnother = new EntitySet<T>(itemsFromAnother.Select(x => (T)x));

            result = this.Subtract(typedAnother)
                .Select(x => (IEntity)x);
        }

        public void Intersect(IEntitySet another, out IEnumerable<IEntity> result)
        {
            IEnumerable<IEntity> itemsFromAnother;

            another.GetAll(out itemsFromAnother);

            EntitySet<T> typedAnother = new EntitySet<T>(itemsFromAnother.Select(x => (T)x));

            result = this.Intersect(typedAnother)
                .Select(x => (IEntity)x);
        }

        #endregion        
    }
}
